from flask import Blueprint,session
from app.extensions import api,cache,db
from flask_restful import Resource,reqparse,inputs,fields,marshal
from app.util import send_verification_code,random_string
from app.models import User
from werkzeug.security import generate_password_hash,check_password_hash
import uuid
import random

user=Blueprint('user',__name__) # 创建蓝本对象，第一个参数是蓝本名，不能与其他蓝本名重复；第二个参数是要导入蓝本的模块的名；第三个参数是统一给蓝本里路由添加的前缀，可不写
api.init_app(user) # 将蓝图绑定到api，而不是以前的app。若蓝图设了url_prefix，则访问api绑定的路由时要加上蓝图前缀(可在app的__init__.py中打印app.url_map查看)。from https://flask-restful.readthedocs.io/en/latest/intermediate-usage.html#use-with-blueprints
parser1=reqparse.RequestParser(bundle_errors=True)
parser1.add_argument('mobile',type=inputs.regex(r'^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$'),help='请输入11位手机号',required=True,location=['form','args']) # 短信登录时的手机号。申请重置密码时用的get，所以加上args，它对应GET请求
parser2=parser1.copy() # copy后相互独立，改变其中一个，另一个不变，新对象里也有mobile
parser2.add_argument('code',type=inputs.regex(r'^\d{6}$'),help='请输入6位验证码',required=True,location='form') # 短信登录时的验证码
parser3=parser1.copy()
parser3.add_argument('image_code',type=inputs.regex(r'^[a-zA-Z0-9]{4}$'),help='请输入4位验证码',required=True) # 申请重置密码时的验证码。因类视图里是get，所以不设location为form
parser4=parser1.copy()
parser4.add_argument('code',type=inputs.regex(r'^\d{6}$'),help='请输入6位验证码',required=True,location='form') # 修改密码时的验证码
parser4.add_argument('password',type=inputs.regex(r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$'),help='密码必须包含大小写字母和数字的组合，可以使用特殊字符，长度在8-16之间',required=True,location='form') # 正则from http://c.runoob.com/front-end/854
parser4.add_argument('password_confirm',type=inputs.regex(r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$'),help='密码必须包含大小写字母和数字的组合，可以使用特殊字符，长度在8-16之间',required=True,location='form') # add_argument()里无法判断两次密码是否一致，所以只能写得跟原来一样
parser5=parser1.copy()
parser5.add_argument('password',type=str,help='请输入密码',required=True,location='form') # 密码登录时的密码
user_fields={
    'id':fields.Integer,
    'username':fields.String
}


class SendVerificationCodeResource(Resource): # 短信登录时发送验证码的类视图
    def post(self):
        args=parser1.parse_args()
        mobile=args.get('mobile')
        return send_verification_code(mobile)


class LoginOrRegisterResource(Resource): # 短信登录或注册的类视图
    def post(self):
        args=parser2.parse_args()
        mobile=args.get('mobile')
        code=args.get('code')
        if cache.get(mobile) and code==cache.get(mobile): # 如果cache中取值时不为None，且和输入的验证码相等。验证码是纯数字，不用转小写
            u=User.query.filter(User.phone==mobile,User.is_deleted==False).first()
            if not u: # 数据库中没这个手机号，注册
                u=User()
                u.phone=mobile
                u.username='用户'+random_string(14)
                db.session.add(u)
                db.session.commit()
            token=str(uuid.uuid4()).replace('-','')+str(random.randint(100,999)) # uuid4()基于随机数生成uuid对象(from https://www.cnblogs.com/lijingchn/p/5299000.html)，再转为字符串(字符串中有多个'-')，再将字符串中的'-'去掉，后面再跟随机的三位数，整体作为token
            cache.set(token,mobile,timeout=60*60*24*7) # 保存用户状态，即是否已登录，且注册后自动登录
            # return marshal(u,user_fields) # 因这里返回值的格式和下面的不同，所以不能用marshal_with()，因它定义了方法内所有的返回值格式
            return {'message':'用户登录或注册成功','status':200,'token':token} # 用了token后返回值不用上一行的
        else:
            return {'error':'验证码错误','status':400}


class ForgotPasswordResource(Resource): # 忘记密码的类视图。github页写的forgot
    def get(self):
        code=random_string(4)
        session['code']=code
        return {'code':code} # 只返回验证码中的字符，交给前端绘制验证码图片


class ApplyResetPasswordResource(Resource): # 申请重置密码的类视图
    def get(self):
        args=parser3.parse_args()
        mobile=args.get('mobile')
        image_code=args.get('image_code')
        if session.get('code') and image_code.lower()==session.get('code').lower(): # 如果session中取值时不为None，且和输入的验证码相等。验证码是字母和数字，要转小写
            u=User.query.filter(User.phone==mobile,User.is_deleted==False).first()
            if u:
                return send_verification_code(mobile)
            else:
                return {'error':'该号码未注册','status':400}
        else:
            return {'error':'验证码错误或超时','status':400}


'''
class ModifyPasswordResource(Resource): # 修改密码的类视图，已移入用户的类视图
    def post(self):
        args=parser4.parse_args()
        mobile=args.get('mobile')
        code=args.get('code')
        if cache.get(mobile) and code==cache.get(mobile): # 如果cache中取值时不为None，且和输入的验证码相等。验证码是纯数字，不用转小写
            u=User.query.filter(User.phone==mobile).first() # 申请重置密码时已验证该用户是否存在，存在才能走到这里，所以这里不用再验证
            password=args.get('password')
            password_confirm=args.get('password_confirm')
            if password==password_confirm: # 判断两次输入的密码是否一致。短信注册时不用设密码，所以数据库里没密码，所以这里没判断新密码是否与旧密码一致
                u.password=generate_password_hash(password) # 存储加密的密码
                db.session.commit()
                return {'message':'密码修改成功','status':200}
            else:
                return {'error':'两次密码不一致','status':400}
        else:
            return {'error':'验证码错误或超时','status':400}
'''


class UserResource(Resource): # 用户的类视图
    def post(self): # 密码登录
        args=parser5.parse_args()
        mobile=args.get('mobile')
        password=args.get('password')
        u=User.query.filter(User.phone==mobile).first()
        if u:
            if check_password_hash(u.password,password): # 第一个参数是数据库中正确的密码，第二个参数是前端输入的密码
                token=str(uuid.uuid4()).replace('-','')+str(random.randint(100,999)) # uuid4()基于随机数生成uuid对象(from https://www.cnblogs.com/lijingchn/p/5299000.html)，再转为字符串(字符串中有多个'-')，再将字符串中的'-'去掉，后面再跟随机的三位数，整体作为token
                cache.set(token,mobile,timeout=60*60*24*7) # 保存用户状态，即是否已登录
                return {'message':'用户登录成功','status':200,'token':token}
        return {'error':'用户名或密码错误','status':400}

    def put(self): # 修改密码
        args=parser4.parse_args()
        mobile=args.get('mobile')
        code=args.get('code')
        if cache.get(mobile) and code==cache.get(mobile): # 如果cache中取值时不为None，且和输入的验证码相等。验证码是纯数字，不用转小写
            u=User.query.filter(User.phone==mobile).first() # 申请重置密码时已验证该用户是否存在，存在才能走到这里，所以这里不用再验证
            password=args.get('password')
            password_confirm=args.get('password_confirm')
            if password==password_confirm: # 判断两次输入的密码是否一致。短信注册时不用设密码，所以数据库里没密码，所以这里没判断新密码是否与旧密码一致
                u.password=generate_password_hash(password) # 存储加密的密码
                db.session.commit()
                return {'message':'密码修改成功','status':200}
            else:
                return {'error':'两次密码不一致','status':400}
        else:
            return {'error':'验证码错误或超时','status':400}


# 接口功能和流程仿今日头条
# 短信登录时发送验证码，由于发送前加了判断是否是Ajax请求，所以postman里要在Headers加上请求头(内容在判断代码处)，Body里KEY填mobile(前面设置的)而不是phone
api.add_resource(SendVerificationCodeResource,'/send_verification_code') # 若蓝图设了url_prefix，则访问api绑定的路由时要加上蓝图前缀(可在app的__init__.py中打印app.url_map查看)，前提是蓝图绑定到api，而不是以前的app。
api.add_resource(LoginOrRegisterResource,'/login_via_code') # 无论登录还是注册，最终都是登录，所以起名login。postman的Body的KEY里填mobile和code
api.add_resource(ForgotPasswordResource,'/forgot_password') # github页写的forgot
api.add_resource(ApplyResetPasswordResource,'/apply_reset_password') # 点击忘记密码后出现验证码图片，验证通过后转到申请重置密码的页面，这个页面内给手机号发送验证码。由于给手机号发送验证码的函数加了判断是否是Ajax请求，所以postman里要在Headers加上请求头(内容在判断代码处)
api.add_resource(UserResource,'/user') # put方法是申请重置密码的短信验证码发送之后，在这里接收，及接收新密码并修改密码